# -*- coding: UTF-8 -*-
# @Time      : 2022/6/18 10:08
# @Author    : IT Pyramid
# @File      : qt
import json
import os
import re
import shutil
import socket
import sys
from datetime import datetime
import uvicorn
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from fastapi import FastAPI
from backend.app.app.crud_pub.authentication import Authentication
from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox, QWidget, QSystemTrayIcon, QAction, QMenu, \
    QStackedLayout
from PyQt5 import QtGui, QtWidgets
from threading import Thread
from backend.app.app.crud_pub.ui_main import Ui_MainWindow
from backend.app.app.crud_pub.closeWindow import Ui_Form as CloseWindowUI
from backend.app.app.crud_pub.ui_config import Ui_Form as ConfigUI
from backend.app.app.crud_pub.ui_project import Ui_Form as ProjectUI
from backend.app.app.db.base import load_config, update_config as base_update_config


class ProjectMain(QMainWindow):

    def __init__(self):
        super(ProjectMain, self).__init__()
        self.thread = None
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.auth = Authentication()
        self.tp = QSystemTrayIcon(self)
        self.config = load_config("./db/config.json")['config']
        # print("config", self.config)
        self.server_port = self.config.get('server_port', "8889")
        self.config_window = ConfigWindow(self.config)
        self.project_window = ProjectWindow()
        self.init_ui()
        self.close_window = CloseWindow()

    def init_ui(self):
        self.setFixedSize(self.width(), self.height())
        self.tp.setIcon(QIcon("./static/images/icon.png"))
        tp_show = QAction('&显示(show)', self.tp, triggered=self.show)
        tp_close = QAction('&关闭(close)', self.tp, triggered=self.close)
        tp_menu = QMenu()
        tp_menu.addAction(tp_show)
        tp_menu.addAction(tp_close)
        self.tp.setContextMenu(tp_menu)
        self.tp.activated.connect(self.act)
        self.stack_layout = QStackedLayout(self.ui.main_window)
        self.stack_layout.addWidget(self.project_window)
        self.stack_layout.addWidget(self.config_window)
        self.stack_layout.setCurrentIndex(0)
        self.init_fuc()

    def init_fuc(self):
        """
        信号和槽
        """
        # 当点击主界面按钮时stacklayout切换到主界面
        self.ui.main_interface.triggered.connect(lambda: self.stack_layout.setCurrentIndex(0))
        # 当点击配置按钮时stacklayout切换到系统配置页面
        self.ui.project_config.triggered.connect(lambda: self.stack_layout.setCurrentIndex(1))

        self.project_window.ui.b_start_project_2.clicked.connect(self.start_project)
        self.project_window.ui.b_stop_project_2.clicked.connect(self.stop_project)
        self.config_window.ui.update.clicked.connect(self.update_config)
        self.config_window.ui.reset.clicked.connect(self.reset_config)
        self.ui.import_auth.triggered.connect(self.import_auth_file)
        self.ui.create_auth.triggered.connect(self.create_auth_file)
        self.ui.about.triggered.connect(self.project_about)

    def act(self, reason):
        if reason in [2, 3]:
            self.show()

    def import_auth_file(self):
        """
        导入授权文件
        """
        auth_file, fileType = QtWidgets.QFileDialog.getOpenFileName(self, "选取文件", os.getcwd(), "Text Files(*.txt)")
        # 存放授权文件的路径
        auth_file_path = os.path.join(os.getcwd(), "authorizeOut.txt")
        if auth_file == "":
            return None
        try:
            if os.path.exists(auth_file_path):
                res_code = QMessageBox.warning(self, "确认", "授权文件已存在, 是否覆盖?", QMessageBox.Yes | QMessageBox.No)
                # 若用户选择No, 则不操作文件
                if res_code == 65536:
                    return None
            try:
                shutil.copyfile(auth_file, auth_file_path)
            except Exception as e:
                QMessageBox.about(self, "失败", "授权文件导入失败, {}".format(e))
                return None
            QMessageBox.about(self, "成功", "授权文件导入成功")
            self.project_window.ui.qt_terminal_2.insertPlainText(
                "\n" + datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S") + "成功导入授权文件\n")
            return None

        except Exception as e:
            print(e)
            QMessageBox.about(self, "失败", "授权文件导入失败")
            self.project_window.ui.qt_terminal_2.insertPlainText("\n授权文件导入失败 " + e + "\n")

    def create_auth_file(self):
        """
        生成授权信息文件
        """
        file_path = QtWidgets.QFileDialog.getExistingDirectory(self, "选择存储路径", "C:/")
        if file_path == "":
            return None
        auth_file = os.path.join(file_path, "AuthorizeFile.txt")
        try:
            auth_data = self.auth.encryption(json.dumps({"cpu": self.auth.get_cpu(), "mac": self.auth.get_mac()}))
            with open(auth_file, "w") as f:
                f.write(auth_data)
            QMessageBox.about(self, "成功", "生成授权信息文件成功")
            self.project_window.ui.qt_terminal_2.insertPlainText(
                datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S") + " 生成授权信息文件")
            self.project_window.ui.qt_terminal_2.insertPlainText("\n文件存储路径: " + auth_file + "\n")
        except Exception as e:
            print(e)
            QMessageBox.about(self, "失败", "生成授权信息文件失败")
            self.project_window.ui.qt_terminal_2.insertPlainText("\n生成授权信息文件失败: " + e + "\n")

    @staticmethod
    def check_port_status(port):
        """
        查看某个端口是否正在呗监听
        @port: 服务器监听端口
        @return-> dict: 服务运行状态和进程ID
        """
        status = False
        order = "netstat -ano|findstr {}".format(port)
        # 查找所有使用port端口的进程
        pid_info = os.popen(order).read()
        # 存放进程ID
        pid_list = set()
        if pid_info:
            # 使用命令读取的进程信息是通过\n连接的，根据\n分割成每一条数据，格式如下
            #   TCP    127.0.0.1:8889         0.0.0.0:0              LISTENING       18272
            pids = pid_info.split("\n")
            # 遍历进程信息，查找进程状态为LISTENING的进程
            for pid in pids:
                # 将每条信息根据空格分割，获取进程状态和进程号
                if pid:
                    # ['TCP', '127.0.0.1:8889', '0.0.0.0:0', 'LISTENING', '18272']
                    p_info = pid.split()
                    p_state = p_info[-2]
                    p_id = p_info[-1]
                    if p_state == 'LISTENING':
                        pid_list.add(p_id)
        if pid_list:
            status = True
        return {'status': status, 'pid_list': pid_list}

    @staticmethod
    def kill_pids(order: str, pid_list: list):
        # print("kill_pids", pid_list)
        for pid in pid_list:
            os.system(order % pid)

    def start_project(self):
        """
        启动项目，并将启动信息放入文本框中
        """
        try:
            project_status = self.check_port_status(self.server_port)
            if project_status['status']:
                self.project_window.ui.qt_terminal_2.setPlainText("项目已启动\n\n详情请查看日志文件：logs.txt\n")
                return None
            auth_data = self.auth.authentic()
            self.thread = Thread(target=self.sub_start_project, args=(auth_data,))
            self.thread.start()
        except Exception as e:
            print(e + "start_pro")
            self.project_window.ui.qt_terminal_2.insertPlainText(e + e.__traceback__.tb_lineno)

    def sub_start_project(self, auth_data):
        try:
            self.project_window.ui.qt_terminal_2.setPlainText("")
            # print(auth_data)
            if not auth_data['res']:
                self.project_window.ui.qt_terminal_2.insertPlainText(
                    "\n项目激活状态: {}\n原因: {}".format("未激活\n", auth_data['error']))
                return False
            self.project_window.ui.qt_terminal_2.insertPlainText("\n设备校验通过，正在启动项目...\n")
            self.project_window.ui.qt_terminal_2.insertPlainText("\n正在检测 {} 端口...\n".format(self.server_port))
            self.project_window.ui.qt_terminal_2.insertPlainText("\n项目启动成功\n")
            self.project_window.ui.qt_terminal_2.insertPlainText(
                "\n接口根IP为http://{}:{}\n".format('{}'.format(socket.gethostbyname(socket.gethostname())),
                                                self.server_port))
            self.project_window.ui.qt_terminal_2.insertPlainText(
                "\n详情请查看日志文件：{}\n".format(os.path.join(os.getcwd(), "logs.log")))

            project_status = self.check_port_status(self.server_port)
            if not project_status['status']:
                # os.system("uvicorn --host 0.0.0.0 --port {} qt:app --lifespan on".format(self.server_port))
                # uvicorn.run(app=app, host="0.0.0.0", port=int(self.server_port), reload=False)
                os.system("main.exe 40c7bc25c943b9e8977636aafe5d69e9")
        except Exception as e:
            print(e + "sub_start_pro")
            self.project_window.ui.qt_terminal_2.setPlainText("\n项目启动失败，{}\n".format(e))

    def stop_project(self):
        """
        关闭项目
        """
        try:
            res_code = QMessageBox.warning(self, "关闭项目", "项目即将关闭，是否继续？", QMessageBox.Yes | QMessageBox.No)
            # 若用户选择No, 则不操作
            if res_code == 65536:
                return None
            # 若用户选择是，则执行关闭项目方法
            self.run_sub_stop_project()
            # sys.exit(0)
        except Exception as e:
            print("e", e)
            self.project_window.ui.qt_terminal_2.insertPlainText(e)

    def run_sub_stop_project(self):
        """
        判断项目是否在运行，若在运行，则开启线程，执行关闭项目的方法。
        @return: None
        """
        project_status = self.check_port_status(self.server_port)
        if project_status['status']:
            self.thread = Thread(target=self.sub_stop_project, args=(project_status.get("pid_list", []),))
            self.thread.start()
        else:
            self.project_window.ui.qt_terminal_2.setPlainText("\n项目未启动\n")

    def sub_stop_project(self, pid_list):
        """
        关闭项目
        @param pid_list: 监听项目的进程ID列表
        @return: 无返回值
        """
        try:
            order = "kill -9 %s"
            if self.auth.get_platform() == "win":
                order = "taskkill /pid %s -t -f"
            self.kill_pids(order, pid_list)
            self.project_window.ui.qt_terminal_2.insertPlainText("\n正在关闭项目....\n\n项目关闭成功\n")
        except Exception as e:
            self.project_window.ui.qt_terminal_2.insertPlainText("\n" + e + "\n")
            return False

    def project_about(self):
        """
        用户若已经激活，则展示激活时间等信息
        """
        auth_data = self.auth.authentic()
        if auth_data['res']:
            display_data = "\n项目激活状态: {}\n项目到期时间: {}\n在线人数限制: {}\n".format("已激活", auth_data['e_date'],
                                                                           auth_data['online_num'])
        else:
            display_data = "\n项目激活状态: {}\n原因: {}\n".format("未激活", auth_data['error'])
        QMessageBox.about(self, "项目信息", display_data)

    def closeEvent(self, event: QtGui.QCloseEvent) -> None:
        """
        重写closeEvent方法，在窗口关闭时执行自己的方法
        """
        event.ignore()
        self.close_window.show()
        self.close_window.ui.pushButton.clicked.connect(self.close_project)

    def close_project(self):
        """
        最小化或关闭项目
        """
        r1 = self.close_window.ui.r1.isChecked()
        r2 = self.close_window.ui.r2.isChecked()
        if r1:
            project_status = self.check_port_status(self.server_port)
            if project_status['status']:
                res_code = QMessageBox.warning(self, "项目运行", "项目正在运行，退出会导致项目关闭，是否继续退出？",
                                               QMessageBox.Yes | QMessageBox.No)
                # 若用户选择继续退出，则关闭项目
                if not res_code == 65536:
                    self.run_sub_stop_project()
                    # 项目退出
                    sys.exit(0)
                else:
                    self.tp.hide()
            else:
                sys.exit(0)
        if r2:
            self.hide()
            self.tp.show()
        self.close_window.hide()

    @staticmethod
    def change_front_port(server_port):
        """
        修改前端访问的端口
        @return:
        """
        file_path = "../front/assets/Net.txt"
        # file_path = "E:\\LocalServer\\project\\front\\assets\\Net.txt"
        with open(file_path, 'r', encoding='utf8') as fp:
            config = fp.read()
        pattern = r'"\d*"'
        config = re.sub(pattern, '"{}"'.format(server_port), config, re.S)
        with open(file_path, "w", encoding='utf8') as fp:
            fp.write(config)

    def update_config(self):
        """
        更新配置信息
        @return:
        """
        project_status = self.check_port_status(self.server_port)
        try:
            if project_status['status']:
                res_code = QMessageBox.warning(self, "项目运行", "项目正在运行，请先关闭项目", QMessageBox.Yes)
            else:
                db_host = self.config_window.ui.db_host.text()
                print(db_host)
                db_username = self.config_window.ui.db_username.text()
                db_password = self.config_window.ui.db_passwd.text()
                server_port = self.config_window.ui.server_port.text()
                # 是否更新过配置文件
                is_update = 0
                # 判断配置信息是否发生变化，若无发生变化，则不做任何操作
                if not (db_host == self.config.get("host") and db_username == self.config.get(
                        "username") and db_password == self.config.get("password")):
                    # 测试数据库是否可以连通
                    import pymysql
                    try:
                        conn = pymysql.connect(
                            host=db_host,
                            port=3306,
                            user=db_username,
                            passwd=db_password
                        )
                        conn.close()
                        conf = base_update_config({
                            'host': db_host,
                            'username': db_username,
                            'password': db_password
                        }, conf_path="db/config.json")
                        if conf:
                            self.config = conf['config']
                            self.server_port = self.config.get('server_port', "8889")
                            self.config_window.init_conf(self.config)
                            if is_update == 0:
                                is_update = 1
                            elif is_update == 2:
                                is_update = 3

                    except Exception as e:
                        print(e)
                        print(e.__traceback__.tb_lineno)
                        QMessageBox.warning(self, "数据库连接失败", "请检查数据库的IP地址，用户名和密码，是否有误", QMessageBox.Yes)
                        is_update = 0
                # 同上判断
                if not (server_port == self.config.get('server_port')):
                    # 测试端口是否被占用
                    port_status = self.check_port_status(server_port)['status']
                    if port_status:
                        QMessageBox.warning(self, "服务监听端口失败", "服务监听端口已被占用，请切换其他端口", QMessageBox.Yes)
                        is_update = 0
                    else:
                        try:
                            conf = base_update_config({'server_port': server_port}, conf_path="db/config.json")
                            if conf:
                                self.config = conf['config']
                                self.server_port = server_port
                                self.change_front_port(server_port)
                                self.config_window.init_conf(self.config)
                                # 如果数据库修改过了，则is_update=1
                                # 如果服务器数据修改了，则is_update=2
                                # 若数据库和服务器同时修改，则is_update=3
                                if is_update == 0:
                                    is_update = 2
                                elif is_update == 1:
                                    is_update = 3
                        except Exception as e:
                            QMessageBox.warning(self, "服务监听端口失败", "出现未知错误, {}".format(e), QMessageBox.Yes)
                if is_update == 1:
                    QMessageBox.about(self, "配置更新", "数据库配置更新成功")
                elif is_update == 2:
                    QMessageBox.about(self, "配置更新", "服务器配置更新成功")
                elif is_update == 3:
                    QMessageBox.about(self, "配置更新", "服务器和数据库配置更新成功")

        except Exception as e:
            print(e)
            print(e.__traceback__.tb_lineno)

    def reset_config(self):
        """
        一键恢复默认配置
        @return:
        """
        project_status = self.check_port_status(self.server_port)
        if project_status['status']:
            res_code = QMessageBox.warning(self, "项目运行", "项目正在运行，请先关闭项目", QMessageBox.Yes)
        else:
            try:
                res_code = QMessageBox.warning(self, "恢复默认设置", "您正在恢复默认配置，此操作可能导致项目无法启动！请谨慎操作！",
                                               QMessageBox.Yes | QMessageBox.No)
                # 若用户选择No, 则不操作
                if res_code == 65536:
                    return None
                conf = {
                    "server_port": "8889",
                    "host": "127.0.0.1",
                    "username": "root",
                    "password": "root"
                }
                self.config_window.init_conf(conf)
                QMessageBox.about(self, "配置更新", "已恢复默认配置，点击更新配置后生效。")
            except Exception as e:
                print(e)
                print(e.__traceback__.tb_lineno)


class CloseWindow(QWidget):

    def __init__(self):
        super().__init__()
        self.ui = CloseWindowUI()
        self.ui.setupUi(self)
        self.init_ui()

    def init_ui(self):
        # uic.loadUi("backend/", self)
        self.setFixedSize(self.width(), self.height())
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.ui.r1.setChecked(True)


class ConfigWindow(QWidget):
    """
    系统配置子窗口
    """

    def __init__(self, conf):
        super().__init__()
        self.ui = ConfigUI()
        self.ui.setupUi(self)
        self.init_conf(conf)

    def init_conf(self, conf):
        """
        初始化配置信息
        @return:
        """
        self.ui.db_host.setText(conf.get('host'))
        self.ui.db_username.setText(conf.get("username"))
        self.ui.db_passwd.setText(conf.get("password"))
        self.ui.server_port.setValue(int(conf.get("server_port")))


class ProjectWindow(QWidget):
    """
    项目启动子窗口
    """

    def __init__(self):
        super().__init__()
        self.ui = ProjectUI()
        self.ui.setupUi(self)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    p = ProjectMain()
    p.show()
    sys.exit(app.exec_())
    # print(str(uvicorn.run(app, host="127.0.0.1", port=8889, reload=False)))
